﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Collections.Specialized;
using System.Web;
using System.Reflection;
using System.IO;

namespace FHJ.FHPI.PushNotifications.Library.GoogleC2DM
{
	/// <summary>
	/// Wrapper class offering methods to make the necessary requests to Google's C2DM services.
	/// </summary>
	public static class RequestHandler
	{
		private const String AUTH_REQUEST_URL = "https://www.google.com/accounts/ClientLogin";

        /// <summary>
        /// Url to the "Send C2DM message" service. (
        /// <remarks>The correct Url is the same but with Https, but
        /// Google's SSL certificate is invalid (e.g. see ).
        /// Therefore we have to use it without Https)</remarks>
        /// </summary>
        private const String SENDMESSAGE_REQUEST_URL = "http://android.apis.google.com/c2dm/send";

		/// <summary>
		/// Performs the authentication request to Google's client login service.
		/// </summary>
		/// <param name="userName">The username of the account.</param>
		/// <param name="passWord">The password of the account.</param>
		/// <returns>The value of the authentication token.</returns>
		public static String DoAuthTokenRequest(String userName, String passWord)
		{
			try
			{
				Assembly callingAssembly = Assembly.GetCallingAssembly();
				Version assemblyVersion = callingAssembly.GetName().Version;

				// Prepare request...
				NameValueCollection parameters = new NameValueCollection();
				parameters.Add("accountType", "HOSTED_OR_GOOGLE");
				parameters.Add("Email", userName);
                parameters.Add("Passwd", passWord);
                parameters.Add("source", String.Format("FH Joanneum-FHPI Push Notifications Server Library-{0}", assemblyVersion.ToString()));
				parameters.Add("service", "ac2dm");

				Byte[] response = Http.Post(AUTH_REQUEST_URL, parameters);

                NameValueCollection responseValues = ParseC2DMRequestResponse(response);

				if (responseValues.AllKeys.Contains("Auth"))
					return responseValues["Auth"];
				else
					throw new Exception(Properties.Resources.Res_000001);
			}
			catch (WebException)
			{
				throw;
			}
			catch (Exception)
			{
				throw;
			}
		}

		/// <summary>
		/// Performs a send message request asynchronously.
		/// </summary>
		public static void DoSendMessageRequestAsync(
            Object requestId,
			String deviceId,
			String authToken,
			String messageType,
            String additionalInfo,
            UploadValuesCompletedEventHandler eventHandler)
		{
			try
			{
                // Prepare request...
                NameValueCollection parameters = new NameValueCollection();
                NameValueCollection headers = new NameValueCollection();

                parameters.Add("registration_id", deviceId);
                parameters.Add("collapse_key", Guid.NewGuid().ToString());
                if (!String.IsNullOrEmpty(messageType))
                    parameters.Add("data.MessageType", messageType);
                if (!String.IsNullOrEmpty(additionalInfo))
                    parameters.Add("data.AdditionalInfo", additionalInfo);

                headers.Add("Authorization", String.Concat("GoogleLogin auth=", authToken));

                // Start the request...
                Http.PostAsync(
                    SENDMESSAGE_REQUEST_URL,
                    parameters,
                    requestId,
                    eventHandler,
                    headers);
			}
			catch (Exception)
			{                
				throw;
			}
		}

		/// <summary>
		/// Parses the values of the ClientLogin authentication request.
		/// </summary>
        /// <param name="responseBytes">The raw byte[] response received.</param>
		/// <returns>A pair of Keys/Values.</returns>
		public static NameValueCollection ParseC2DMRequestResponse(Byte[] responseBytes)
		{
			try
			{
                String response = Encoding.ASCII.GetString(responseBytes);

				List<String> lines = new List<string>();
				using (StringReader sr = new StringReader(response))
				{
					String currentLine = null;
					while (!((currentLine = sr.ReadLine()) == null))
						lines.Add(currentLine);
				}

				NameValueCollection values = new NameValueCollection();

				String currentKey = null;
				StringBuilder currentValue = null;

				foreach (String line in lines)
				{
					if (line.Length > 0)
					{
						if (line.Contains("="))
						{
							if (!(currentKey == null))
								values.Add(currentKey, currentValue.ToString());

							currentKey = line.Substring(0, line.IndexOf("="));
							currentValue = new StringBuilder();
							if (line.IndexOf("=") + 1 < line.Length)
								currentValue.Append(line.Substring(line.IndexOf("=") + 1));
						}
						else
						{
							currentValue.Append(line);
						}
					}
				}

				if (!(currentKey == null))
					values.Add(currentKey, currentValue.ToString());

				return values;
			}
			catch (Exception)
			{                
				throw;
			}
		}

        /// <summary>
        /// Parses a retry after header.
        /// </summary>
        /// <param name="response"></param>
        /// <returns>If not specified, or cannot be parsed, returns null; otherwise the Timestamp (in our local time</returns>
        public static DateTime? ParseRetryAfterHeader(HttpWebResponse response)
        {
            try
            {
                if (response.Headers.AllKeys.Contains("Retry-After"))
                {
                    String value = response.Headers["Retry-After"];
                    if (!String.IsNullOrEmpty(value))
                    {
                        Int32 deltaSeconds = 0;
                        if (Int32.TryParse(value, out deltaSeconds))
                            return DateTime.Now.AddSeconds(deltaSeconds);
                        else
                        {
                            // For date formats see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
                            DateTime dateParse;
                            if (DateTime.TryParse(value, out dateParse))
                                return dateParse.ToLocalTime();
                            else if (DateTime.TryParseExact(value, "ddd MMM  d HH:mm:ss yyyy", System.Globalization.CultureInfo.GetCultureInfo("en-GB"), System.Globalization.DateTimeStyles.None, out dateParse))
                                return dateParse.ToLocalTime();
                            else
                                // Specified retry-after cannot be parsed, so ignore and return null...
                                return null;
                        }
                    }
                    else
                        return null; // Header specified but not available...
                }
                else
                    return null; // No header specified...
            }
            catch (Exception)
            {                
                throw;
            }
        }
	}
}
